package com.example.link.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.example.common.common.config.bloom.BloomFilterConfig;
import com.example.common.utils.RedisUtils;
import com.example.link.entity.LinkAccessLog;
import com.example.link.entity.ShortLink;
import com.example.link.mapper.LinkAccessLogMapper;
import com.example.link.mapper.ShortLinkMapper;
import com.example.link.service.LinkAccessService;
import com.example.link.util.HttpAnalysisUtil;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @author qwert
 * @version 1.0.0
 * @create 2025/8/18 23:19
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class LinkAccessServiceImpl implements LinkAccessService {
    private final static String SHORT_LINK_PREFIX = "short_link_prefix:";
    private final static String SHORT_LINK_SETNX_PREFIX = "short_link_setnx_prefix";
    private static final String INVALID_URL_MARKER = "INVALID_URL";

    private static final int LOCK_KEY_EXPIRE_TIME = 10000;
    private static final int MAX_WAIT_TIME = 3000;

    private final ShortLinkMapper shortLinkMapper;
    private final RedisUtils redisUtils;
    private final BloomFilterConfig.BloomFilterWrapper bloomFilterWrapper;
    private final LinkAccessLogMapper linkAccessLogMapper;


    @Override
    public void restoreUrl(String shortUri, ServletRequest request, ServletResponse response) {
        String originalUrl = "http://localhost:5173/short-link";

        // 获取短链接
        String shortUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/" + shortUri;

        // 查询缓存
        String cacheKey = SHORT_LINK_PREFIX + shortUrl;
        if (redisUtils.hasKey(cacheKey)) {
            originalUrl = getCacheShortUrl(cacheKey);
        } else {
            // 尝试获取分布式锁
            String lockKey = SHORT_LINK_SETNX_PREFIX + shortUrl;
            try {
                if (getLockKey(lockKey, shortUrl)) {
                    // 双重检查锁
                    if (redisUtils.hasKey(cacheKey)) {
                        originalUrl = getCacheShortUrl(cacheKey);
                    } else {

                        // 布隆过滤器校验
                        if (!bloomFilterWrapper.exists(shortUri)) {
                            redisUtils.set(cacheKey, INVALID_URL_MARKER, 10 * 60, TimeUnit.SECONDS);
                            throw new RuntimeException("无效链接");
                        }

                        // 获取锁，执行数据库查询
                        // 查询数据库
                        ShortLink shortLink = shortLinkMapper.queryLinkByShortUrl(shortUrl);
                        // 数据库判空，转发原链接
                        if (shortLink != null) {
                            originalUrl = shortLink.getOriginalUrl();
                            redisUtils.set(cacheKey, JSONObject.toJSONString(shortLink), 10 * 60, TimeUnit.SECONDS);
                        } else {
                            redisUtils.set(cacheKey, INVALID_URL_MARKER, 10 * 60, TimeUnit.SECONDS);
                            throw new RuntimeException("无效链接");
                        }
                    }
                }
            } finally {
                redisUtils.releaseLock(lockKey, shortUrl);
            }
        }

        // 异步记录访问日志
        asyncLogAccess(request, shortUrl, originalUrl);

        try {
            ((HttpServletResponse) response).sendRedirect(originalUrl);
        } catch (IOException e) {
            throw new RuntimeException("跳转失败");
        }
    }

    private void asyncLogAccess(ServletRequest request, String shortUrl, String originalUrl) {
        try {
            LinkAccessLog accessLog = HttpAnalysisUtil.buildAccessLog(request, shortUrl, originalUrl);
            linkAccessLogMapper.insertLinkAccessLog(accessLog);
            // 非阻塞入队，队列满时丢弃
//            accessLogQueue.offer(accessLog);
        } catch (Exception e) {
            // 记录日志收集异常，但不影响主流程
            log.warn("访问日志收集失败: {}", e.getMessage());
        }
    }

    /**
     * 循环尝试获取分布式锁（带超时控制）
     * @param lockKey 锁的key
     * @param lockValue 锁的值
     * @return true-获取成功，false-获取失败
     */
    private boolean getLockKey(String lockKey, String lockValue) {
        long startTime = System.currentTimeMillis();
        long maxWaitEndTime = startTime + MAX_WAIT_TIME;

        // 指数退避参数
        long waitTime = 50; // 初始等待时间
        final long maxWaitInterval = 500; // 最大等待间隔
        final long backoffMultiplier = 2; // 退避倍数

        while (System.currentTimeMillis() < maxWaitEndTime) {
            // 尝试获取锁
            if (redisUtils.tryLock(lockKey, lockValue, LOCK_KEY_EXPIRE_TIME)) {
                return true;
            }

            // 检查是否超时
            if (System.currentTimeMillis() >= maxWaitEndTime) {
                break;
            }

            // 指数退避等待
            try {
                Thread.sleep(Math.min(waitTime, maxWaitEndTime - System.currentTimeMillis()));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }

            // 增加等待时间（指数退避）
            waitTime = Math.min(waitTime * backoffMultiplier, maxWaitInterval);
        }

        return false;
    }


    public String getCacheShortUrl(String cacheKey) {
        String cacheUrl = redisUtils.get(cacheKey);
        if (cacheUrl.equals(INVALID_URL_MARKER)) {
            throw new RuntimeException("无效链接");
        }
        ShortLink cacheShortLink = JSONObject.parseObject(cacheUrl, ShortLink.class);
        return cacheShortLink.getOriginalUrl();
    }
}
